/**
 * @file main.c
 * @author 郭一锋
 * @date 2025.6.3-2025.6.5
 * @brief Linux ls命令的C语言实现（支持-l/-a/-r/-t/-R/-h参数组合）
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <stdint.h>
#include <time.h>
#include <math.h>

/* 常量定义 */
#define OUTPUT_ROW 64       ///< 输出结果数组的行数限制
#define OUTPUT_COL 512      ///< 每行输出的最大字符数
#define MAX_SUB_DIR 32      ///< 递归子目录的最大数量
#define MAX_NAME 4096       ///< 文件/目录名的最大长度

/**
 * @struct ls_options
 * @brief 存储ls命令的选项标志
 * @author 郭一锋
 * @date 2025.6.3
 */
struct ls_options {
    int long_format;   ///< -l 长格式显示
    int show_all;      ///< -a 显示隐藏文件
    int reverse_sort;  ///< -r 反向排序
    int sort_by_time;  ///< -t 按修改时间排序
    int recursive;     ///< -R 递归子目录
    int human;         ///< -h 人类可读文件大小
};

int output_row; ///< 当前输出结果的行数计数器

/**
 * @brief 打印格式化后的输出结果
 * @param data 二维数组存储的输出内容
 * @param dir_num 传入的目录参数个数
 * @param opt 选项标志结构体
 * @author 郭一锋
 * @date 2025.6.3
 * @note 根据是否启用长格式(-l)调整换行策略
 */
void print_outputdata(char data[][OUTPUT_COL], int dir_num, struct ls_options opt) // 输出ls的结果
{
    // 不以长格式输出时，一个空行代表一个换行符，每行后面不带\n
    // 长格式每行后面带\n
    if (opt.long_format == 0) // 无-l
    {
        for (int i = 0; i < output_row; i++)
        {
            if (data[i][0] == '\0')
            {
                printf("\n");
                continue;
            }
            printf("%s ", data[i]);
        }
        printf("\n");
    }
    else if (opt.long_format == 1) // 有-l
    {
        for (int i = 0; i < output_row; i++)
        {
            if (data[i][0] == '\0')
            {
                printf("\n");
                continue;
            }
            printf("%s\n", data[i]);
        }
    }
}

/**
 * @brief 解析命令行参数，分离选项和目录路径
 * @param option 存储解析出的选项字符（如"lartRh"）
 * @param dir 存储目录路径的指针数组
 * @param argc main函数传入的参数个数
 * @param argv main函数传入的参数数组
 * @return 解析出的目录参数个数
 * @author 郭一锋
 * @date 2025.6.3
 */
int get_opt_and_arg(char *option, char **dir, int argc, char *argv[]) // 返回目录个数
{
    int j = 0;       // 用于分配目录空间
    int opt_pos = 0; // 用于写option数组
    for (int i = 0; i < argc; i++)
    {
        if (argv[i][0] == '-') // 如果该字符串是选项
        {                      // 记录命令选项
            int k = 1;
            while (argv[i][k] != '\0')
            {
                option[opt_pos] = argv[i][k];
                k++;
                opt_pos++;
            }
        }
        else
        { // 记录目录
            if (i == 0)
            {
                continue; // 不记录程序名
            }
            int len = strlen(argv[i]);
            dir[j] = (char *)malloc((len + 1) * sizeof(char));
            strcpy(dir[j], argv[i]);
            j++;
        }
    }
    return j; // 返回目录个数
}

/**
 * @brief 将月份缩写转换为数字（1-12）
 * @param month 3字母月份缩写（如"Jan"）
 * @return 月份序号（1-12）
 * @author 郭一锋
 * @date 2025.6.4
 */
int parse_month(const char *month)
{
    const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    for (int i = 0; i < 12; i++)
    {
        if (strncmp(month, months[i], 3) == 0)
            return i;
    }
    return 0;
}

/**
 * @brief 默认文件名排序比较函数（按字母顺序）
 * @param a 第一个文件名
 * @param b 第二个文件名
 * @return 比较结果（同strcmp）
 * @author 郭一锋
 * @date 2025.6.3
 */
int compare_name_defaut(const void *a, const void *b)
{
    return strcmp((const char *)a, (const char *)b);
}

/**
 * @brief 长格式下的文件名比较函数
 * @param a 第一个长格式字符串
 * @param b 第二个长格式字符串
 * @return 比较结果（比较最后一个空格后的文件名部分）
 * @author 郭一锋
 * @date 2025.6.4
 */
int compare_name_defaut_longformat(const void *a, const void *b)
{
    const char *str_a = (const char *)a;
    const char *str_b = (const char *)b;

    // 从字符串末尾向前查找最后一个空格
    const char *name_a = strrchr(str_a, ' ');
    const char *name_b = strrchr(str_b, ' ');

    // 如果找到空格，指针+1跳过空格；否则使用整个字符串
    name_a = (name_a ? name_a + 1 : str_a);
    name_b = (name_b ? name_b + 1 : str_b);

    return strcmp(name_a, name_b);
}

/**
 * @brief 反转普通格式的输出顺序
 * @param output_data 输出结果数组
 * @param begin 起始行索引
 * @param end 结束行索引
 * @author 郭一锋
 * @date 2025.6.4
 */
void compare_name_with_reverse(char output_data[][OUTPUT_COL], int begin, int end)
{
    for (int i = begin, j = end - 1; i < j; i++, j--)
    {
        char temp[OUTPUT_COL];

        // 安全拷贝交换
        strncpy(temp, output_data[i], OUTPUT_COL - 1);
        temp[OUTPUT_COL - 1] = '\0';

        strncpy(output_data[i], output_data[j], OUTPUT_COL - 1);
        output_data[i][OUTPUT_COL - 1] = '\0';

        strncpy(output_data[j], temp, OUTPUT_COL - 1);
        output_data[j][OUTPUT_COL - 1] = '\0';
    }
}

/**
 * @brief 反转长格式的输出顺序
 * @param output_data 输出结果数组
 * @param begin 起始行索引
 * @param end 结束行索引
 * @author 郭一锋
 * @date 2025.6.4
 */
void compare_name_with_reverse_longformat(char output_data[][OUTPUT_COL], int begin, int end){
    for (int i = begin, j = end - 1; i < j; i++, j--)
    {
        char temp[128];
        strcpy(temp, output_data[i]);
        strcpy(output_data[i], output_data[j]);
        strcpy(output_data[j], temp);
    }
}

/**
 * @brief 长格式下的时间排序比较函数
 * @param a 第一个长格式字符串
 * @param b 第二个长格式字符串
 * @return 比较结果（按修改时间降序）
 * @author 郭一锋
 * @date 2025.6.4
 * @note 解析格式示例："Jun 03 07:20"
 */
int compare_name_with_time_longformat(const void *a, const void *b)
{
    const char *str_a = (const char *)a;
    const char *str_b = (const char *)b;

    // 示例时间格式： "Jun 03 07:20"
    char month_a[4], month_b[4];
    int day_a, day_b, hour_a, hour_b, min_a, min_b;

    sscanf(strrchr(str_a, ' ') - 11, "%3s %d %d:%d", month_a, &day_a, &hour_a, &min_a);
    sscanf(strrchr(str_b, ' ') - 11, "%3s %d %d:%d", month_b, &day_b, &hour_b, &min_b);

    // 转换为可比较的数值
    int time_a = parse_month(month_a) * 10000 + day_a * 1000 + hour_a * 100 + min_a;
    int time_b = parse_month(month_b) * 10000 + day_b * 1000 + hour_b * 100 + min_b;

    return time_b - time_a;
}

/**
 * @brief 计算目录的total值（块数*512）
 * @param path 目录路径
 * @param opt 选项标志（主要检查-a）
 * @return 目录总大小（以512字节块为单位）
 * @author 郭一锋
 * @date 2025.6.4
 */
long calculate_dir_total(const char *path, struct ls_options opt){ // 计算单个目录的 total 值（不递归子目录）
    DIR *dir = opendir(path);
    if (!dir)
    {
        perror("opendir failed");
        return -1;
    }

    long total_blocks = 0;
    struct dirent *entry;
    struct stat entry_stat;

    while ((entry = readdir(dir)) != NULL)
    {
        if (opt.show_all == 0)
        { // 不要求统计所有文件
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || entry->d_name[0] == '.')
            { // 跳过"." , ".."和隐藏文件
                continue;
            }
        }

        char full_path[1024];
        snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name); // 构建完整路径

        if (lstat(full_path, &entry_stat) == -1) // 获取文件信息
        {
            perror("lstat failed");
            continue;
        }

        total_blocks += entry_stat.st_blocks;
    }

    closedir(dir);
    return total_blocks; // 以字节作为单位，如果返回5代表统计了5*512;
}

/**
 * @brief 非长格式下的目录内容打印
 * @param dir 目标目录路径
 * @param output_data 输出结果数组
 * @param dir_num 传入的目录参数个数
 * @param opt 选项标志结构体
 * @author 郭一锋
 * @date 2025.6.4
 * @note 处理普通文件名输出和递归逻辑
 */
void print_single_filename_in_dir(char *dir, char output_data[][OUTPUT_COL], int dir_num, struct ls_options opt)
{ // 打印传入目录中的文件名到二维数组output_data中,并完成排序
    DIR *cur_dir = opendir(dir);
    if (!cur_dir)
    {
        perror("opendir failed");
        return;
    }

    if (dir_num > 1 || opt.recursive == 1)
    {
        strcpy(output_data[output_row], dir);
        output_data[output_row][strlen(dir)] = ':';
        output_row += 2;
    }

    int begin = output_row; // 用于qsort排序

    char sub_dir[MAX_SUB_DIR][MAX_NAME] = {0}; // 用于存放递归的子集
    int sub_dir_row = 0;                       // 用于写入sub_dir

    struct dirent *entry;
    while ((entry = readdir(cur_dir)) != NULL)
    {                          // 文件名打印到二维数组中，readdir的读取顺序是不固定的，与底层的文件系统实现有关
        if (opt.show_all == 0) // 未输入-a
        {
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
            { // 跳过"."和".."
                continue;
            }
            else if (entry->d_name[0] == '.') // 读到隐藏文件
            {
                continue;
            }
        }

        if (opt.recursive == 1) // 如果需要递归子目录
        {
            if (entry->d_type == DT_DIR) // 如果读到的文件类型是目录
            {
                if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                {
                    // 如果是.或..，跳过
                }
                else // 将目录完整路径放入待递归的目录中
                {
                    char fullpath[MAX_NAME] = {0};
                    snprintf(fullpath, sizeof(fullpath), "%s/%s", dir, entry->d_name);
                    strcpy(sub_dir[sub_dir_row++], fullpath);
                }
            }
        }

        strncpy(output_data[output_row], entry->d_name, sizeof(output_data[0]) - 1); // 写入文件名

        output_data[output_row][sizeof(output_data[0]) - 1] = '\0';
        output_row++;
    }

    int end = output_row;

    if (opt.sort_by_time == 1)
    {
        // 待补充
    }
    else
    {
        qsort(output_data + begin, end - begin, sizeof(output_data[0]), compare_name_defaut);
    }

    if (opt.reverse_sort == 1)
    {
        compare_name_with_reverse(output_data, begin, end);
    }

    if (dir_num > 1 || opt.recursive == 1)
    {
        output_row += 2; // 控制输出格式
    }

    if (sub_dir_row > 0)
    { // 递归子目录
        for (int i = 0; i < sub_dir_row; i++)
        {
            print_single_filename_in_dir(sub_dir[i], output_data, 2, opt); // dir_num参数用于控制输出格式，所以传入2，显示路径名
        }
    }

    closedir(cur_dir);
}

/**
 * @brief 长格式下的目录内容打印
 * @param dir 目标目录路径
 * @param output_data 输出结果数组
 * @param dir_num 传入的目录参数个数
 * @param opt 选项标志结构体
 * @author 郭一锋
 * @date 2025.6.5
 * @note 包含权限、链接数、所有者、大小等详细信息
 */
void print_longformat_in_dir(char *dir, char output_data[][OUTPUT_COL], int dir_num, struct ls_options opt) {// 以长格式写入output_data
    if (dir_num > 1 || opt.recursive == 1) // 如果传入多目录或需要递归查找子目录，则显示目录名
    {
        strcpy(output_data[output_row], dir);
        output_data[output_row][strlen(dir)] = ':';
        output_row++;
    }

    // 计算total
    if (opt.human == 0) // 不加-h
    {
        long n = calculate_dir_total(dir, opt);
        double size;
        if (n > 1)
        {
            size = n * 512.0; // 块转字节
            int unit_idx = 0;
            while (size >= 1024 && unit_idx < 3)
            {
                size /= 1024;
            }
            size = ceil(size); // 向上取整
        }
        else
        {
            size = 1.0;
        }
        sprintf(output_data[output_row], "total %.0lf", size);
    }
    else // 以人类易读方式显示
    {
        long n = calculate_dir_total(dir, opt);
        double size;
        if (n > 1) // 转化为KB
        {
            size = n * 512.0;
            const char *units[] = {"B", "K", "M", "G"};
            int unit_idx = 0;
            while (size >= 1024 && unit_idx < 3)
            {
                size /= 1024;
                unit_idx++;
            }
            size=ceil(size);
            sprintf(output_data[output_row], "total %.0lf%s", size, units[unit_idx]);
        }
        else // 只显示512(B)
        {
            size = 512.0;
            sprintf(output_data[output_row], "total %.0lf", size);
        }
    }
    output_row++;

    DIR *cur_dir = opendir(dir);
    if (!cur_dir)
    {
        perror("opendir failed");
        return;
    }

    int begin = output_row; // 用于给文件排序

    char sub_dir[MAX_SUB_DIR][MAX_NAME] = {0}; // 用于存放递归的子集
    int sub_dir_row = 0;                       // 用于写入sub_dir

    struct dirent *entry;
    while ((entry = readdir(cur_dir)) != NULL)
    {
        if (opt.show_all == 0) // 未输入-a
        {
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
            { // 跳过"."和".."
                continue;
            }
            else if (entry->d_name[0] == '.')
            {
                continue;
            }
        }

        if (opt.recursive == 1) // 如果需要递归子目录
        {
            if (entry->d_type == DT_DIR) // 如果读到的文件类型是目录
            {
                if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                {
                    // 如果是.或..，跳过
                }
                else // 将目录完整路径放入待递归的目录中
                {
                    char fullpath[MAX_NAME] = {0};
                    snprintf(fullpath, sizeof(fullpath), "%s/%s", dir, entry->d_name);
                    strcpy(sub_dir[sub_dir_row++], fullpath);
                }
            }
        }

        int pos = 0; // 用于写每一行
        struct stat file_stat;
        char fullpath[MAX_NAME];
        snprintf(fullpath, sizeof(fullpath), "%s/%s", dir, entry->d_name); // 拼接路径

        if (stat(fullpath, &file_stat) == -1)
        {
            perror("stat failed");
            continue;
        }

        // 构造每一行
        output_data[output_row][pos++] = (S_ISDIR(file_stat.st_mode)) ? 'd' : '-';  // 目录/文件类型
        output_data[output_row][pos++] = (file_stat.st_mode & S_IRUSR) ? 'r' : '-'; // 用户读
        output_data[output_row][pos++] = (file_stat.st_mode & S_IWUSR) ? 'w' : '-'; // 用户写
        output_data[output_row][pos++] = (file_stat.st_mode & S_IXUSR) ? 'x' : '-'; // 用户执行
        output_data[output_row][pos++] = (file_stat.st_mode & S_IRGRP) ? 'r' : '-'; // 组读
        output_data[output_row][pos++] = (file_stat.st_mode & S_IWGRP) ? 'w' : '-'; // 组写
        output_data[output_row][pos++] = (file_stat.st_mode & S_IXGRP) ? 'x' : '-'; // 组执行
        output_data[output_row][pos++] = (file_stat.st_mode & S_IROTH) ? 'r' : '-'; // 其他读
        output_data[output_row][pos++] = (file_stat.st_mode & S_IWOTH) ? 'w' : '-'; // 其他写
        output_data[output_row][pos++] = (file_stat.st_mode & S_IXOTH) ? 'x' : '-'; // 其他执行
        output_data[output_row][pos++] = ' ';
        int written = snprintf(output_data[output_row] + pos, OUTPUT_COL - pos, "%lu", (unsigned long)file_stat.st_nlink); // 硬链接数
        pos += written;
        output_data[output_row][pos++] = ' ';

        struct passwd *pw = getpwuid(file_stat.st_uid);
        memcpy(output_data[output_row] + pos, pw->pw_name, strlen(pw->pw_name)); // 所有者名
        pos += strlen(pw->pw_name);
        output_data[output_row][pos++] = ' ';

        struct group *gr = getgrgid(file_stat.st_gid);
        memcpy(output_data[output_row] + pos, gr->gr_name, strlen(gr->gr_name)); // 所在组名
        pos += strlen(gr->gr_name);
        output_data[output_row][pos++] = ' ';

        if (opt.human == 0) // 默认输出
        {
            written = snprintf(output_data[output_row] + pos, OUTPUT_COL - pos, "%jd", (intmax_t)file_stat.st_size); // 文件大小
            pos += written;
        }
        else // 以人类易读方式输出
        {
            const char *units[] = {"B", "K", "M", "G", "T", "P"};
            int unit_idx = 0;
            double size = file_stat.st_size;

            // 计算合适单位
            while (size >= 1024 && unit_idx < 5)
            {
                size /= 1024;
                unit_idx++;
            }

            // 写入带单位的大小（保留1位小数）
            written = snprintf(output_data[output_row] + pos, OUTPUT_COL - pos, "%.1f%s", size, units[unit_idx]);
            pos += written;
        }

        char time_str[32];
        struct tm *tm_info = localtime(&file_stat.st_mtime);
        strftime(time_str, sizeof(time_str), "%b  %-d %H:%M", tm_info);
        written = snprintf(output_data[output_row] + pos, OUTPUT_COL - pos, " %s", time_str); // 写入最后修改时间
        pos += written;
        output_data[output_row][pos++] = ' ';

        memcpy(output_data[output_row] + pos, entry->d_name, strlen(entry->d_name));
        pos += strlen(entry->d_name);
        output_data[output_row][pos] = '\0';

        output_row++; // 下一行
    }

    int end = output_row;

    if (opt.sort_by_time == 1)
    { // 按时间顺序排序
        qsort(output_data + begin, end - begin, sizeof(output_data[0]), compare_name_with_time_longformat);
    }
    else
    { // 默认顺序
        qsort(output_data + begin, end - begin, sizeof(output_data[0]), compare_name_defaut_longformat);
    }

    if (opt.reverse_sort == 1)
    { // 将已经排好的结果进行逆序排序
        compare_name_with_reverse_longformat(output_data, begin, end);
    }

    if (dir_num > 1 || opt.recursive == 1)
    { // 控制输出格式
        output_row++;
    }

    if (sub_dir_row > 0)
    { // 递归子目录
        for (int i = 0; i < sub_dir_row; i++)
        {
            print_longformat_in_dir(sub_dir[i], output_data, 2, opt); // dir_num参数用于控制输出格式，所以传入2，显示路径名
        }
    }
    closedir(cur_dir);
}

/**
 * @brief ls命令主逻辑函数
 * @param option 选项字符串（如"lartRh"）
 * @param dir 目录路径数组
 * @param dir_num 目录参数个数
 * @author 郭一锋
 * @date 2025.6.5
 * @note 根据选项调用不同的打印函数
 */
void my_ls(char *option, char **dir, int dir_num)
{
    struct ls_options opt = {0};

    if (option[0] != '\0') // 如果存在参数，进行解析
    {
        for (int i = 0; i < strlen(option); i++)
        {
            if (option[i] == 'l')
            {
                opt.long_format = 1;
            }
            else if (option[i] == 'a')
            {
                opt.show_all = 1;
            }
            else if (option[i] == 'r')
            {
                opt.reverse_sort = 1;
            }
            else if (option[i] == 't')
            {
                opt.sort_by_time = 1;
            }
            else if (option[i] == 'R')
            {
                opt.recursive = 1;
            }
            else if (option[i] == 'h')
            {
                opt.human = 1;
            }
            else
            {
                continue;
            }
        }
    }

    char output_data[OUTPUT_ROW][OUTPUT_COL] = {0}; // 用于存放输出结果的二维数组
    if (opt.long_format == 0)
    { // 如果不要求输出长格式，则只输出文件名
        if (dir_num > 0)
        {
            for (int i = 0; i < dir_num; i++)
            {
                print_single_filename_in_dir(dir[i], output_data, dir_num, opt); // 把每个目录中的文件名都打印到输出结果数组中
            }
        }
        else // 输出当前目录
        {
            print_single_filename_in_dir(".", output_data, dir_num, opt);
        }

        print_outputdata(output_data, dir_num, opt);
    }
    else if (opt.long_format == 1)
    { // 长格式输出
        if (dir_num > 0)
        {
            for (int i = 0; i < dir_num; i++)
            {
                print_longformat_in_dir(dir[i], output_data, dir_num, opt);
            }
        }
        else
        {
            print_longformat_in_dir(".", output_data, dir_num, opt);
        }

        print_outputdata(output_data, dir_num, opt);
    }
}

/**
 * @brief 程序入口函数
 * @param argc 参数个数
 * @param argv 参数数组
 * @return 程序退出状态
 * @author 郭一锋
 * @date 2025.6.5
 */
int main(int argc, char *argv[])
{
    char option[8] = {0};                                      // 用于存储选项，最多支持8个
    char **dir = (char **)malloc((argc - 2) * sizeof(char *)); // 分配多目录的存储空间

    int dir_num = get_opt_and_arg(option, dir, argc, argv); // 获取参数列表与目录

    my_ls(option, dir, dir_num); // ls实现函数

    if (dir_num > 0) // 释放空间
    {
        for (dir_num--; dir_num >= 0; dir_num--)
        {
            free(dir[dir_num]);
        }
    }
    free(dir);

    return 0;
}
